/**
*******************************************************
*@file  rs485.c
*@brief rs485.c
*@author 花满楼
*@date     2019-5-24
*@version  V1
*@attention none
*******************************************************
*/
#include "rs485.h"
#include "delay.h"
#include "crc16.h"

_Control_RegisterGroup Control_Reg;	///<控制结构体句柄
UART_HandleTypeDef UART3_RS485Handler;


#define Dial_up0    PEin(0)     ///<拨码开关第0位
#define Dial_up1    PEin(1)<<1  ///<拨码开关第1位
#define Dial_up2    PEin(2)<<2  ///<拨码开关第2位
#define Dial_up3    PEin(3)<<3  ///<拨码开关第3位


//接收缓存区
u8 RS485_RX_BUF[64];    /**<接收缓冲,最大64个字节.//接收到的数据长度*/
u8 Flag_RX_485;         /**<1--接收到一帧校验正确的数据但尚未响应*/
u16 Handle_CNT_485;     /**<0-99  可以处理的请求帧的百分比*/
u16 Handle_Percent_485; /**<0-99  可以处理的请求帧的百分比*/
u16 Flag_TX_485;        /**<1--正在发送数据*/
u16 FrameLength_485;    /**<发送或接收的帧的字节数*/
u16 Bytes_TX_485;       /**<已发送的数据字节数*/
u16 Bytes_RX_485;       /**<已接收的数据字节数*/
u16 Flag_FrameStart_485;  /**<1--开始接收除地址和功能码外的其他帧数据*/
u8 Data_BUF_485[40];      /**<接收数据缓存数组*/

u32 Address_485 = 0;
u8 RS485_TX_BUF[100];
u16 *ModBus_485;
u16 Alarm_Data[10];          /**<MODBUS的告警量    对应clecet中的地址 */
u16 Control_Date[10];        /**< MODBUS的控制量*/
u16 Set_Dress[20];           /**<MODBUS的设置量*/
u16 Analog_Data[150];        /**<u16数据的485通讯数组*/
u8 Flag_ParaChange_485;      /**<1--上位机写数据*/
u16 count_485;  /**<通讯计数，10s内无数据发送，可复位uart*/

u8 CRC_ERR; /**<接收数据校验CRC错误*/
u8 Pack_Addr = 0x00; /**<pack地址*/

/**
*******************************************************
*@brief 初始化IO 串口3
*@param[in]   bound:波特率
*@return  none
*@note    none
*******************************************************
*/
void RS485_Init(u32 bound)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOC_CLK_ENABLE();     //使能GPIOC时钟
    __HAL_RCC_GPIOD_CLK_ENABLE();     //使能GPIOE时钟
    __HAL_RCC_USART3_CLK_ENABLE();          //使能UART3时钟
    GPIO_Initure.Pin = GPIO_PIN_10;
    GPIO_Initure.Mode = GPIO_MODE_AF_PP;  //复用推挽输出
    GPIO_Initure.Pull = GPIO_PULLUP;    //上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速
    GPIO_Initure.Alternate = GPIO_AF7_USART3; //复用为UART3
    HAL_GPIO_Init(GPIOC, &GPIO_Initure);    //初始化PC11.10
    GPIO_Initure.Pin = GPIO_PIN_11;
    HAL_GPIO_Init(GPIOC, &GPIO_Initure);    //初始化PC11.10

    //USART 初始化设置
    UART3_RS485Handler.Instance = USART3;           //UART3
    UART3_RS485Handler.Init.BaudRate = bound;         //波特率
    UART3_RS485Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
    UART3_RS485Handler.Init.StopBits = UART_STOPBITS_1; //一个停止位
    UART3_RS485Handler.Init.Parity = UART_PARITY_NONE;  //无奇偶校验位
    UART3_RS485Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
    UART3_RS485Handler.Init.Mode = UART_MODE_TX_RX;     //收发模式
    HAL_UART_Init(&UART3_RS485Handler);             //HAL_UART_Init()会使能USART3
    __HAL_UART_DISABLE_IT(&UART3_RS485Handler, UART_IT_TC);
    __HAL_UART_ENABLE_IT(&UART3_RS485Handler, UART_IT_RXNE);    //开启接收中断和发送完成中断
    HAL_NVIC_EnableIRQ(USART3_IRQn);                //使能USART3中断
    HAL_NVIC_SetPriority(USART3_IRQn, 2, 3);            //抢占优先级2，子优先级3
    Flag_RX_485 = 0;    //1--接收到一帧校验正确的数据但尚未响应
    Flag_TX_485 = 0;    //1--正在发送数据
    Flag_FrameStart_485 = 0;  //1--开始接收除地址和功能码外的其他帧数据
    Flag_ParaChange_485 = 0;  //1--触摸屏向ST写数据
}

/**
*******************************************************
*@brief usart3中断
*@note  RS485_RX_BUF[0] == 0x00是广播地址
        RS485_RX_BUF[0] == 0x16是设备地址，其中设备地址尚未设置
*******************************************************
*/
void USART3_IRQHandler(void)
{
    u8 res;
	Pack_Addr =0x16;//测试使用
    if ((__HAL_UART_GET_FLAG(&UART3_RS485Handler, UART_FLAG_RXNE) != RESET))        //接收中断
    {
        HAL_UART_Receive(&UART3_RS485Handler, &res, 1, 1000);
        RS485_RX_BUF[0] = RS485_RX_BUF[1];
        RS485_RX_BUF[1] = res;    //记录接收到的值

        //计算数据包的处理率
        if (((RS485_RX_BUF[0] == Pack_Addr) || (RS485_RX_BUF[0] == 0x00)) && ((RS485_RX_BUF[1] == 0x03)  || (RS485_RX_BUF[1] == 0x10)))
        {
            Receive_CNT_485++;

            if (Flag_RX_485 == 0)
            {
                Handle_CNT_485++;
            }

            if (Receive_CNT_485 >= 60000)
            {
                Handle_Percent_485 = Handle_CNT_485;
                Receive_CNT_485 = 0;
                Handle_CNT_485 = 0;
            }
        }

        //接收并存储数据帧
        if ((Flag_RX_485 == 0) && (Flag_TX_485 == 0))
        {
            if (((RS485_RX_BUF[0] == Pack_Addr) || (RS485_RX_BUF[0] == 0x00)) && (RS485_RX_BUF[1] == 0x03) && (Flag_FrameStart_485 == 0))
            {
                Bytes_RX_485 = 2;
                Flag_FrameStart_485 = 1;
                //03功能码请求帧固定为8个字节
                FrameLength_485 = 8;
                Data_BUF_485[0] = RS485_RX_BUF[0];
                Data_BUF_485[1] = RS485_RX_BUF[1];
                return;
            }

            if (((RS485_RX_BUF[0] == Pack_Addr) || (RS485_RX_BUF[0] == 0x00)) && (RS485_RX_BUF[1] == 0x10) && (Flag_FrameStart_485 == 0))
            {
                Bytes_RX_485 = 2;
                Flag_FrameStart_485 = 1;
                //10功能码请求帧固定长度部分为 从机地址、功能码、寄存器起始地址和数量、要写的字节数、CRC
                FrameLength_485 = 9;
                Data_BUF_485[0] = RS485_RX_BUF[0];
                Data_BUF_485[1] = RS485_RX_BUF[1];
                return;
            }

            if (Flag_FrameStart_485 == 1)
            {
                if (Bytes_RX_485 < FrameLength_485)
                {
                    //此处处理不当，极易超出数组长度，修改其他变量，引发严重后果。应采取保护措施
                    Data_BUF_485[Bytes_RX_485] = res;
                    Bytes_RX_485++;

                    if ((Data_BUF_485[1] == 0x10) && (Bytes_RX_485 == 7))  
                    {
                        FrameLength_485 = FrameLength_485 + Data_BUF_485[6]; //Data_BUF_485[6]为要写的字节数
                    }

                    if (Bytes_RX_485 == FrameLength_485)   //已接收完整的一帧数据
                    {
                        Total_CNT_485++;
                        Flag_FrameStart_485 = 0;

                        if (Data_BUF_485[1] == 0x03)
                        {
                            CRC_485 = CRC16(Data_BUF_485, 6);
                            Flag_RX_485 = 1;

                            if (CRC_485 == ((Data_BUF_485[6] << 8) | Data_BUF_485[7]))
                            {
                                Frame_CNT_485++;
                                CRC_ERR = 0;
                            }
                            else
                            {
                                CRC_ERR = 1;
                            }
                        }

                        if (Data_BUF_485[1] == 0x10)
                        {
                            CRC_485 = CRC16(Data_BUF_485, Data_BUF_485[6] + 7);
                            Flag_RX_485 = 1;

                            if (CRC_485 == ((Data_BUF_485[Data_BUF_485[6] + 7] << 8) | Data_BUF_485[Data_BUF_485[6] + 8]))
                            {
                                Frame_CNT_485++;
                                CRC_ERR = 0;
                            }
                            else
                            {
                                CRC_ERR = 1;
                            }
                        }

                        //统计CRC校验的正确率
                        if (Total_CNT_485 >= 60000)
                        {
                            Frame_Percent_485 = Frame_CNT_485;
                            Total_CNT_485 = 0;
                            Frame_CNT_485 = 0;
                        }
                    }//if(Bytes_RX_485==FrameLength_485)
                }//if(Bytes_RX_485<FrameLength_485)
            }//if(Flag_FrameStart_485==1)
        }//if((Flag_RX_485==0)&&(Flag_TX_485==0))
    }

    if ((__HAL_UART_GET_FLAG(&UART3_RS485Handler, UART_IT_TXE) != RESET))        //接收中断
    {
        Flag_TX_485 = 0;
    }

    HAL_UART_IRQHandler(&UART3_RS485Handler);    //调用HAL库中断处理公用函数
}

/**
*******************************************************
*@brief RS485发送数据
*@param[in]   buf:发送区首地址
*@param[in]   len:发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
*@return  none
*@note    none
*******************************************************
*/
void RS485_Send_Data(u8 *buf, u8 len)
{
    RS485_RX_EN = 1;    //接收数据开关关闭
	RS485_WR_EN = 1;	//发送数据开关开启
    HAL_UART_Transmit(&UART3_RS485Handler, buf, len, 10);    //串口3发送数据
    RS485_RX_EN = 0;    //接收数据开关打开
	RS485_WR_EN = 0;	//发送数据开关关闭
    count_485 = 0;  //复位UART计数
}
/**
*******************************************************
*@brief RS485接收数据分析，并发送相应的数据；如果10s内没有数据发送，认为存在通讯中断
*@param[in]   none
*@return  0:设备通讯中断；1：设备通讯正常
*@attention
                通讯协议中的非法帧有 寄存器不存在、请求寄存器个数非法、帧长度小于5、帧长度大于256、CRC错误5中
				但实际中仅适用了寄存器不存在、请求寄存器个数非法、CRC错误
*******************************************************
*/
u8 RS485_Data(void)
{
    u32 i = 0;
    u8 ADDR_Invalid = 0; //485的地址无效，0：有效；1：无效

    if ((Flag_RX_485 == 1) && (Flag_TX_485 == 0))
    {
        Address_485 = (Data_BUF_485[2] << 8) + Data_BUF_485[3];   //03 10对应的地址处在帧的相同位置

        if ((Data_BUF_485[1] == 0x03) || (Data_BUF_485[1] == 0x10))
        {
            if (Address_485 <= 0x0E)
            {
                ModBus_485 = (u16 *)(&Analog_Data[Address_485]);  //定义模拟量的地址，去除+1的操作
            }
            else
                if (Address_485 <= 0x77)
                {
                    ModBus_485 = (u16 *)(&Analog_Data[Address_485]); //模拟量
                }
                else
                    if (Address_485 <= 0x8B)
                    {
                        ModBus_485 = (u16 *)(&Set_Dress[Address_485 - 0x82]);  //设置量
                    }
                    else
                        if (Address_485 <= 0x99)
                        {
                            ModBus_485 = (u16 *)(&Alarm_Data[Address_485 - 96]);    //告警量
                        }
                        else
                            if (Address_485 <= 0xA3)
                            {
                                ModBus_485 = (u16 *)(&Control_Date[Address_485 - 0xA0]);    //设置量
                            }
                            else
                                ADDR_Invalid = 1;   //地址非法
        }

        if (0 != CRC_ERR) //接收数据CRC错误
        {
            //发送的一帧数据的前两个字节总是与接收到的一帧数据的前两个字节相同（从机地址+功能码）
            RS485_TX_BUF[0] = Data_BUF_485[0];
            RS485_TX_BUF[1] = Data_BUF_485[1] + 0x80; //按照通讯协议
            RS485_TX_BUF[2] = 0x00;
            RS485_TX_BUF[3] = 0xB1;
            //计算CRC  +3为从机地址、功能码、字节数
            CRC_485 = CRC16(RS485_TX_BUF, 4);
            //将CRC校验值的高、低字节部分分别存放到SCI发送数组TX_BUF[128]中对应的位置上
            RS485_TX_BUF[4] = CRC_485 >> 8;
            RS485_TX_BUF[5] = CRC_485;
            Flag_TX_485 = 1;
            Bytes_TX_485 = 1;
            FrameLength_485 = 6;   //+5为从机地址、功能码、字节数、CRC
            RS485_Send_Data(RS485_TX_BUF, FrameLength_485);
            Flag_TX_485 = 0;
			Flag_RX_485 = 0;
        }
        else
            if (0 != ADDR_Invalid) //地址非法
            {
                //发送的一帧数据的前两个字节总是与接收到的一帧数据的前两个字节相同（从机地址+功能码）
                RS485_TX_BUF[0] = Data_BUF_485[0];
                RS485_TX_BUF[1] = Data_BUF_485[1] + 0x80; //按照通讯协议
                RS485_TX_BUF[2] = 0x00;
                RS485_TX_BUF[3] = 0x91;
                //计算CRC  +3为从机地址、功能码、字节数
                CRC_485 = CRC16(RS485_TX_BUF, 4);
                //将CRC校验值的高、低字节部分分别存放到SCI发送数组TX_BUF[128]中对应的位置上
                RS485_TX_BUF[4] = CRC_485 >> 8;
                RS485_TX_BUF[5] = CRC_485;
                Flag_TX_485 = 1;
                Bytes_TX_485 = 1;
                FrameLength_485 = 6;   //+5为从机地址、功能码、字节数、CRC
                RS485_Send_Data(RS485_TX_BUF, FrameLength_485);
                Flag_TX_485 = 0;
				Flag_RX_485 = 0;
            }
            else
                if (2220 < ((Data_BUF_485[2] << 8) + Data_BUF_485[3]))       //请求寄存器个数非法
                {
                    //发送的一帧数据的前两个字节总是与接收到的一帧数据的前两个字节相同（从机地址+功能码）
                    RS485_TX_BUF[0] = Data_BUF_485[0];
                    RS485_TX_BUF[1] = Data_BUF_485[1] + 0x80; //按照通讯协议
                    RS485_TX_BUF[2] = 0x00;
                    RS485_TX_BUF[3] = 0xA1;
                    //计算CRC  +3为从机地址、功能码、字节数
                    CRC_485 = CRC16(RS485_TX_BUF, 4);
                    //将CRC校验值的高、低字节部分分别存放到SCI发送数组TX_BUF[128]中对应的位置上
                    RS485_TX_BUF[4] = CRC_485 >> 8;
                    RS485_TX_BUF[5] = CRC_485;
                    Flag_TX_485 = 1;
                    Bytes_TX_485 = 1;
                    FrameLength_485 = 6;   //+5为从机地址、功能码、字节数、CRC
                    RS485_Send_Data(RS485_TX_BUF, FrameLength_485);
                    Flag_TX_485 = 0;
					Flag_RX_485 = 0;
                }
                else
                {
                    if (Data_BUF_485[1] == 0x03)
                    {
                        //发送的一帧数据的前两个字节总是与接收到的一帧数据的前两个字节相同（从机地址+功能码）
                        RS485_TX_BUF[0] = Data_BUF_485[0];
                        RS485_TX_BUF[1] = Data_BUF_485[1];
                        //上位机要读的字节数（因modbus限制一次读取的寄存器数量为1-125个，所以Data_BUF_485[4]=0）
                        RS485_TX_BUF[2] = Data_BUF_485[5] * 2;

                        //将上位机要读的参数拆分成高、低字节后分别存放到UART发送数组TX_BUF[256]中对应的位置上
                        if (Address_485 <= 500)
                            for (i = 0; i < Data_BUF_485[5]; i++)
                            {
                                RS485_TX_BUF[3 + 2 * i] = (*ModBus_485 & 0xFF00) >> 8;   //存放高字节部分，标准modbus协议用
                                RS485_TX_BUF[4 + 2 * i] = (*ModBus_485 & 0x00FF);               //存放低字节部分，标准modbus协议用
                                ModBus_485++;
                            }

                        //计算CRC  +3为从机地址、功能码、字节数
                        CRC_485 = CRC16(RS485_TX_BUF, RS485_TX_BUF[2] + 3);
                        //将CRC校验值的高、低字节部分分别存放到SCI发送数组TX_BUF[128]中对应的位置上
                        RS485_TX_BUF[RS485_TX_BUF[2] + 3] = CRC_485 >> 8;
                        RS485_TX_BUF[RS485_TX_BUF[2] + 4] = CRC_485;
                        Flag_TX_485 = 1;
                        Bytes_TX_485 = 1;
                        FrameLength_485 = RS485_TX_BUF[2] + 5;   //+5为从机地址、功能码、字节数、CRC
                        RS485_Send_Data(RS485_TX_BUF, FrameLength_485);
                        Flag_TX_485 = 0;
                    }

                    if ((Data_BUF_485[1] == 0x10) && (Address_485 + Data_BUF_485[5] <= 400))       //当前程序所有可以写的数据地址均小于400
                    {
                        //将要写的数写到对应的位置上
                        for (i = 0; i < Data_BUF_485[5]; i++)   //Data_BUF_485[5]为要写的寄存器数
                        {
                            *ModBus_485 = ((Data_BUF_485[7 + 2 * i] << 8) | Data_BUF_485[8 + 2 * i]);
                            ModBus_485++;
                        }

                        Flag_ParaChange_485 = 1;
                        //成功写入时发送的一帧数据的前6个字节总是与接收到的一帧数据的前6个字节相同
                        RS485_TX_BUF[0] = Data_BUF_485[0];
                        RS485_TX_BUF[1] = Data_BUF_485[1];
                        RS485_TX_BUF[2] = Data_BUF_485[2];
                        RS485_TX_BUF[3] = Data_BUF_485[3];
                        RS485_TX_BUF[4] = Data_BUF_485[4];
                        RS485_TX_BUF[5] = Data_BUF_485[5];
                        //计算CRC
                        CRC_485 = CRC16(RS485_TX_BUF, 6);
                        //将CRC校验值的高、低字节部分分别存放到SCI发送数组SCI_TX[30]中对应的位置上
                        RS485_TX_BUF[6] = CRC_485 >> 8;
                        RS485_TX_BUF[7] = CRC_485;
                        Flag_TX_485 = 1;
                        Bytes_TX_485 = 1;
                        FrameLength_485 = 8;
                        RS485_Send_Data(RS485_TX_BUF, FrameLength_485);
                        Flag_TX_485 = 0;
                    }

                    Flag_RX_485 = 0;
                }
    }

    /**********************长时间无数据发送，复位串口***************************/
    count_485++;

    if (count_485 >= 10000)      /*长时间接收不到串口数据，认为通讯中断，复位通讯接口*/
    {
        RS485_Init(UART_Bound);   /*重新初始化RS485*/
        count_485 = 10000;
        return 0;   //通讯故障
    }

    return 1;   //设备正常
}
/**
*******************************************************
*@brief modbusRTU的16位CRC校验
*@param[in]   puchMsg：数据
*@param[in]   usDataLen：长度
*@return  none
*@note    none
*******************************************************
*/
u16 CRC16(u8 *puchMsg, u16 usDataLen)
{
    u16 CRC_high = 0xFF;    //高CRC字节初始化
    u16 CRC_low = 0xFF;   //低CRC 字节初始化
    u32 Index;        //CRC循环中的索引

    // 传输消息缓冲区
    while (usDataLen--)
    {
        Index = CRC_high^*puchMsg++;   //计算CRC
        CRC_high = CRC_low ^ CRCTable_high[Index];
        CRC_low = CRCTable_low[Index];
    }

    //返回CRC校验值
    return ((CRC_high << 8) | CRC_low);
}
/**
*******************************************************
*@brief 通过拨码开关获取设备地址
*@param   none
*@return  none
*@note    none
*******************************************************
*/
void Get_Address()
{
    Pack_Addr = 0x16 + (Dial_up0 | Dial_up1 | Dial_up2 | Dial_up3);
}
